10 Ack/Nak 协议(Ack/Nak Protocol)
上一章
在上一章中,我们描述了数据链路层数据包(DLLP)。我们介绍了 DLLP 类型的使用、格式和定义,以及其相关字段的详细信息。DLLP 用于支持 Ack/Nak 协议、电源管理、流量控制机制,并可用于供应商自定义的目的。
本章
本章介绍数据链路层的一个关键特性:一种基于硬件的自动机制,用于确保 TLP 在链路上的可靠传输。Ack DLLP 确认 TLP 的成功接收,而 Nak DLLP 则指示传输错误。我们将描述在未检测到 TLP 或 DLLP 错误时的正常操作规则,以及与 TLP 和 DLLP 错误相关的错误恢复机制。
下一章
下一章将介绍物理层的逻辑子块,该子块负责准备数据包以进行串行传输和接收。完成这一过程需要多个步骤,这些步骤将在本章中详细说明。本章涵盖与使用 8b/10b 编码的前两个规范版本 Gen1 和 Gen2 相关的逻辑。Gen3 的逻辑不使用 8b/10b 编码,将在第 407 页的“物理层 - 逻辑(Gen3)”章节中单独描述。
10.1 目标:可靠的 TLP 传输
数据链路层(如图 10-1 第 318 页所示)的功能是确保 TLP 的可靠传输。规范要求误码率(BER)不高于 10−12,但错误仍会频繁发生并引发问题,且单个比特错误就可能导致整个数据包损坏。随着链路速率随新一代技术持续提升,这一问题将愈发显著。
图 10-1:数据链路层
为实现这一目标,每个 TLP(事务层数据包)都附加了一个称为 LCRC(链路循环冗余校验码)的错误检测代码。错误检查的第一步是验证该代码在接收端是否仍能正确计算。如果每个数据包还分配了唯一的递增序列号,那么就能轻松区分多个已发送数据包中哪个出现了错误。利用该序列号,我们还可以要求 TLP 必须按照发送顺序成功接收。这一简单规则使得在接收端的数据链路层中检测丢失的 TLP 变得容易。
与 Ack/Nak 协议相关的数据链路层基本模块在 319 页的图 10-2 中有更详细的展示。每个通过链路发送的 TLP 都会在接收端通过评估数据包中的 LCRC(首先)和序列号(其次)进行检查。接收设备通过返回 Ack(确认)来通知发送设备已成功接收到一个正确的 TLP。发送端收到 Ack,表示接收端已成功接收到至少一个 TLP。相反,发送端收到 Nak,则表明接收端接收到的至少一个 TLP 存在错误。此时,发送端将重新发送相应的 TLP,以期获得更好的结果。这种做法是合理的,因为导致传输错误的原因通常是瞬时事件,重发极有可能解决问题。
图 10-2:Ack/Nak 协议概览
由于协议中的发送设备和接收设备都同时具备发送端和接收端,本章将使用以下术语:
- 发送端:指发送 TLP 的设备
- 接收端:指接收 TLP 的设备
10.2 Ack/Nak 协议要素
数据链路层的主要 Ack/Nak 协议要素如图 10-3(第 320 页)所示。但由于内容过多,不宜一次性全部考虑,因此我们首先聚焦于发送端要素,其放大视图如图 10-4(第 322 页)所示。
图 10-3:Ack/Nak 协议要素
10.2.1 发送端要素
当来自事务层的 TLP 到达时,会执行多项操作以准备接收端进行可靠的错误检测。如图所示,TLP 首先被分配下一个连续的序列号,该序列号从 12 位的 NEXT_TRANSMIT_SEQ 计数器获取。
10.2.1.1 NEXT_TRANSMIT_SEQ 计数器
该计数器生成将分配给下一个传入 TLP 的序列号。它是一个 12 位计数器,在复位或链路层报告 DL_Down(链路层未激活)时初始化为 0。由于它随每个 TLP 持续递增且仅向前计数,该计数器最终会达到其最大值 4095,并在继续计数时回滚至 0。
分配给 TLP 的这个序列号将用于接收方发送的 Ack 或 Nak,以在重放缓冲区中引用该 TLP。有人可能会认为如此大的计数器意味着大量未确认的 TLP 可能正在传输中,但实际上这种情况非常罕见。主要原因是接收方需要在特定时间内为成功接收的 TLP 发送 Ack。该时间长度在《AckNak_LATENCY_TIMER》第 328 页有详细讨论,但通常仅够传输几个最大尺寸的数据包。
10.2.1.2 LCRC 生成器
该模块根据待发送的头部和数据生成一个 32 位 CRC(循环冗余校验)码,并将其附加到输出数据包的末尾,以便于错误检测。其名称源于该校验码(根据待发送的数据包计算得出)是冗余的(不增加额外信息),且源自循环码。尽管 CRC 无法像 ECC(纠错编码)方法那样为接收方提供足够的自动纠错信息,但它能提供可靠的错误检测能力。CRC 常用于串行传输,因为其易于硬件实现,且擅长检测突发错误(即一连串的错误比特位)。由于串行设计中此类错误发生的概率高于并行模型,这解释了为何 CRC 是串行传输中错误检测的理想选择。CRC 码通过 TLP 的所有字段(包括序列号)计算得出。接收方将执行相同的计算,并将其结果与 TLP 中的 LCRC 字段进行比对。若两者不匹配,则接收方的链路层将检测到错误。
10.2.1.3 重放缓冲区
重放缓冲区(或称重试缓冲区)按照传输顺序存储 TLP,包括序列号和 LCRC。当发送端收到 Ack,表明 TLP 已成功到达接收端时,它会从重放缓冲区中清除序列号等于或早于该 Ack 中编号的 TLP。通过这种方式,设计允许一个 Ack 代表多个成功传输的 TLP,从而减少必须发送的 Ack 数量。由于数据包必须始终按顺序呈现,因此如果收到序列号为 7 的 Ack,则不仅 TLP 7 已成功接收,而且其之前的所有数据包也必然已成功接收,因此无需在重放缓冲区中保留它们的副本。
如果收到 Nak,Nak 中的序列号仍然表示最后一个成功接收的数据包。因此,即使收到 Nak,也可能导致发送端从重放缓冲区中清除 TLP。然而,由于这是 Nak,意味着接收端未能成功接收某些内容,因此在清除所有已确认的 TLP 后,发送端必须按顺序重放重放缓冲区中剩余的所有 TLP。例如,如果收到序列号为 9 的 Nak,则数据包 9 及之前的所有数据包将从重放缓冲区中清除,因为接收端已确认它们被成功接收。但由于这是 Nak,发送端必须随后按顺序重放重放缓冲区中剩余的所有 TLP,从数据包 10 开始。
图 10-4:与 Ack/Nak 协议相关的发送端元素
10.2.1.4 REPLAY_TIMER 计数
该定时器实际上是一个看门狗定时器,用于确保发送端能够接收到已传输 TLP 的 Ack/Nak 数据包。若该定时器超时,则表明发送端已发送一个或多个 TLP,但在预期时间内未收到确认。解决方案是重传重放缓冲区中的所有内容,并重新启动 REPLAY_TIMER。
每当有 TLP 已传输但尚未确认时,该定时器便会运行。若 REPLAY_TIMER 当前未运行,则在任何 TLP 的最后一个符号被传输时启动。若定时器已在运行,则发送额外的 TLP 不会重置定时器值。当接收到确认重放缓冲区中 TLP 的 Ack 或 Nak 时,定时器重置为 0;若重放缓冲区中仍有 TLP(即已传输但尚未确认的 TLP),则立即重新开始计数。然而,若接收到确认重放缓冲区中最后一个 TLP 的 Ack(即重放缓冲区现已清空),则 REPLAY_TIMER 重置为 0 但停止计数。它将不会再次开始计数,直到下一个 TLP 的最后一个符号被传输。
10.2.1.5 REPLAY_NUM 计数
这个 2 位计数器用于跟踪在接收到 Nak 或 REPLAY_TIMER 超时后的重试次数。当 REPLAY_NUM 计数从 11b 回滚到 00b(表示同一组 TLP 的 4 次发送尝试均失败)时,数据链路层会自动强制物理层重新训练链路(LTSSM 进入恢复状态)。重新训练完成后,将再次尝试发送失败的 TLP。REPLAY_NUM 计数器在复位时或链路层处于非活动状态时初始化为 00b。每当接收到序列号比上次更新的序列号更新的 Ack DLLP 时(表示正向进展),该计数器也会被重置。
10.2.1.6 ACKD_SEQ 寄存器
这个 12 位寄存器存储最近接收到的 Ack 或 Nak 的序列号。在复位时或数据链路层处于非活动状态时,该寄存器初始化为全 1。该寄存器通过接收到的 Ack 或 Nak 中的 AckNak_Seq_Num[11:0] 字段进行更新。将 ACKD_SEQ 计数与最后接收到的 Ack 或 Nak 中的序列号进行比较,以检查正向进展。如果最新的 Ack/Nak 的序列号晚于 ACKD_SEQ 寄存器中的值,则表示正在取得正向进展。
顺便提一下,我们使用“更晚的序列号”这一术语,是因为与 PCIe 中的大多数计数器一样,序列号计数器仅向前计数,这意味着它们最终会回绕到零。从技术上讲,更晚的编号应指数值更大的数字,但我们必须记住,当计数器达到 4095(这是一个 12 位计数器)时,下一个更大的数字将是零。这种回绕效应将在后续示例中更容易理解,例如第 331 页的“Ack/Nak 示例”。
如图 10-4(第 322 页)所示,当 Ack 或 Nak 实现向前进展时,它会将序列号等于或早于 DLLP 中值的 TLP 从重放缓冲区中清除。同时,它还会重置 REPLAY_TIMER 和 REPLAY_NUM 计数。如果未实现向前进展,则无法清除任何 TLP,因此我们仅需检查是否为需要重放的 Nak。
这里适合提及计数器的一个潜在问题:发送的 TLP 数量理论上可能远大于接收方已确认的数量。如前所述,这种情况极不可能发生,此处仅出于完整性考虑而提及。该问题本质上与流控制计数器相同(参见第 234 页”阶段 3 - 计数器翻转”),并采用相同的解决方案:NEXT_TRANSMIT_SEQ 和 ACKD_SEQ 计数器之间的差值绝不允许超过其总计数值的一半。如果大量 TLP 在未收到确认的情况下被发送,导致 NEXT_TRANSMIT_SEQ 计数值比 ACKD_SEQ 计数值晚 2048,则在通过接收更多 Ack 解决此问题之前,事务层将不再接受新的 TLP。如果已发送的序列号与已确认计数值之间的差值超过最大计数值的一半,则会报告数据链路层协议错误。(有关错误报告的更多信息,请参见第 655 页”数据链路层错误”。)
10.2.1.7 DLLP CRC 校验
此模块用于检查 DLLP 的 16 位 CRC 错误。若检测到错误,该 DLLP 将被丢弃,并在启用的情况下报告可纠正错误。由于不存在重放或纠正失败 DLLP 的机制,因此无需采取进一步操作。我们只需等待下一次成功的 Ack/Nak,即可使计数器恢复同步,并让正常操作继续执行。
10.2.2 接收端要素
传入的 TLP 首先会检查 LCRC 错误,随后检查序列号。若无错误,该 TLP 将被转发至接收端的事务层。
如果存在错误,TLP 将被丢弃,并且除非已有未处理的 Nak,否则将调度一个 Nak。
第 325 页的图 10-5 展示了与入站 TLP 和出站 Ack/Nak DLLP 处理相关的接收端数据链路层要素。
图 10-5:与 Ack/Nak 协议相关的接收端要素
10.2.2.1 LCRC 错误检查
该模块通过验证 32 位 LCRC 来检查接收到的 TLP 中是否存在传输错误。该模块根据接收到的 TLP 比特位计算 LCRC 值,然后将计算出的 LCRC 与接收到的 LCRC 进行比较。如果两者匹配,则说明数据包的所有比特位均与发送时完全一致。若不匹配,则表明 TLP 中存在比特错误,该数据包将被丢弃,同时发送 Nak 信号以请求重传该数据包及其之后发送的所有 TLP。
10.2.2.2 NEXT_RCV_SEQ 计数器
12 位 NEXT_RCV_SEQ(下一个接收序列号)计数器用于跟踪预期的序列号,并验证数据包是否按序接收。该计数器在复位或数据链路层处于非活动状态时初始化为 0,每成功向事务层转发一个有效 TLP 时递增 1。存在错误或被作废的 TLP 不会发送至事务层,因此不会使该计数器递增。
10.2.2.3 序列号检查
如果 LCRC 校验通过,则检查 TLP 的序列号是否与预期计数(NRS 计数)一致。如第 325 页的图 10-5 所示,此检查可能产生三种结果:
- TLP 序列号等于 NRS 计数(即期望接收的编号)。此时一切正常:该 TLP 被接受并转发至事务层,同时 NRS 计数递增。接收端会调度一个 Ack,但无需立即发送,可等待 AckNak_LATENCY_TIMER 计时器到期。在此期间,可能接收到其他有效的 TLP,使 NEXT_RCV_SEQ 计数器递增。待计时器到期后,将发送一个包含最后接收有效 TLP 序列号(NRS - 1)的单一 Ack。这种方式允许一个 Ack 代表多个成功接收的 TLP,从而减少开销,因为无需为每个 TLP 单独发送 Ack。
- 如果 TLP 的序列号早于 NRS 计数(小于预期值),则说明该 TLP 已被接收过,属于重复数据。只要预期序列号与接收序列号的差值不超过总计数的一半(2048),这就不算错误,仅被视为重复——意味着该 TLP 此前已被接受。此时,该 TLP 会被静默丢弃(不发送 Nak,不报告错误),同时发送一个确认包(Ack),其中包含最后接收到的有效 TLP 的序列号(NRS - 1)。为何会出现这种情况?发送端可能未收到已发送的确认包,导致其 REPLAY_TIMER 超时,从而重发重放缓冲区中的所有数据。通过向发送端发送包含最后接收到的有效数据包序列号的确认包,我们通知其已处理的最新进度。
- 如果 TLP 的序列号晚于 NEXT_RCV_SEQ 计数(逻辑上大于预期值),则说明链路层遗漏了一个 TLP。例如,当预期接收序列号 30,但传入的 TLP 序列号为 31 时,即可判定存在问题。序列号必须连续,既然不连续,则必然有数据包传输失败。
并且已被丢弃;这种情况可能发生在物理层。这个乱序的 TLP 将被丢弃,无论它是否存在其他错误,因为我们必须按顺序接收 TLP,并且如果之前没有未处理的 Nak,则会发送一个 Nak。 预期序列号(NRS)随着新的 TLP 成功接收而递增的概念,以及这如何影响无效序列号范围和重复序列号范围的滑动窗口,可以在图 10-6 中看到。
图 10-6:序列号范围示例
10.2.2.4 NAK_SCHEDULED 标志
每当接收方调度一个 Nak 时,该标志被置位;当接收方成功接收到具有预期序列号(NRS)的 TLP 时,该标志被清除。规范明确指出,在 NAK_SCHEDULED 标志保持置位期间,接收方不得调度额外的 Nak DLLP。作者认为,这是为了防止出现无限循环的可能性;即发送端开始重放某些数据包,但接收方在重放完成前发送了另一个 Nak,导致发送端重新开始发送这些数据包。无论出于何种动机,一旦发送了 Nak,在通过成功接收具有正确序列号的重放 TLP 解决问题之前,将不再有新的 Nak 发出。
10.2.2.5 AckNak_LATENCY_TIMER
该计时器在接收端成功接收到尚未确认的 TLP 时持续运行。一旦计时器超时,接收端必须发送一个 Ack。AckNak 延迟计时器的运行时长由规范规定(参见第 328 页的“AckNak_LATENCY_TIMER”),并决定了接收端可以合并 Ack 的时间长度。当 AckNak 延迟计时器超时后,将生成并发送一个序列号为 NRS - 1 的 Ack,用于指示其接收到的最后一个有效数据包。每当发送 Ack 或 Nak 时,该计时器会重置,并且只有在接收到新的有效 TLP 时才会重新启动。
10.2.2.6 Ack/Nak 生成器
Ack 或 Nak DLLP 由错误检查模块调度,其中包含一个 12 位的 AckNak_Seq_Num 字段,如图 10-7(第 328 页)所示。该值通过将 NRS 计数减 1 得到,用于报告最后一个成功接收的序列号。原因是:成功接收 TLP 后,接收端会先递增 NRS,再调度 Ack;而失败的 TLP 只会调度 Nak,不会递增 NRS。这样可以简化错误数据包的处理,因为 TLP 中的错误可能发生在序列号字段,导致该字段无法用于 Nak。因此,Nak 使用最后一个有效 TLP 的序列号,也就是期望值减 1。唯一的例外是复位后的第一个 TLP:如果这个使用序列号 0 的首个 TLP 失败,则生成的 Nak 的 AckNak_Seq_Num 为全 1(即 0 减 1 的结果)。
图 10-7:Ack 或 Nak DLLP 格式(文本化)
| 字节/位 | 内容 |
|---|---|
| Byte 0,[7:0] | DLLP 类型:0000 0000b =
Ack;0001 0000b = Nak |
| Byte 1,[7:0] | Reserved |
| Byte 2,[7:4] | Reserved |
| Byte 2,[3:0] | AckNak_Seq_Num[11:8] |
| Byte 3,[7:0] | AckNak_Seq_Num[7:0] |
| Byte 4-5 | 16 位 CRC |
表 10-1:Ack 或 Nak DLLP 字段
| 字段名称 | 报头字节/位 | DLLP 功能 |
|---|---|---|
| DLLP 类型 | Byte 0,[7:0] | 指示类型:0000 0000b =
Ack;0001 0000b = Nak。 |
| AckNak_Seq_Num | Byte 2,[3:0];Byte 3,[7:0] | 该值始终为 NEXT_RCV_SEQ 计数减 1。 |
| 16 位 CRC | Byte 4,[7:0];Byte 5,[7:0] | 用于保护此 DLLP 内容的 16 位 CRC。 |
10.3 Ack/Nak 协议详情
本节详细描述了发送端和接收端在处理 TLP 及 Ack/Nak DLLP 时的行为。通过多个示例演示了可能出现的各种情况。
10.3.1 发送端协议详情
10.3.1.1 序列号
参考第 322 页的图 10-4,当 TLP 由事务层传递至链路层时,首要步骤之一是附加一个 12 位序列号。需注意,下一个递增的序列号实际上可能更小,例如当计数器达到最大值 4095 后回滚至零时便会出现这种情况。因此,零值实际上可能”大于”4095 这样的数值。不妨将序列号比较理解为评估一个持续向上移动并循环滚动的”数值窗口”。为阐明这一概念,后续多个示例中都将采用这种计数回滚机制。
10.3.1.2 32 位 LCRC
发送端还会根据 TLP 内容(序列号、头部、数据负载和 ECRC)生成并附加一个 32 位 LCRC(链路 CRC)。
10.3.1.3 重放(重试)缓冲区
10.3.1.3.1 概述
在设备发送 TLP 之前,它会将 TLP 的副本存储在重放缓冲区中。(注意:规范中使用了“重试缓冲区”这一术语,但本书选择使用“重放”而非“重试”,以更清晰地将此机制与旧的 PCI 重试机制区分开来)。每个缓冲区条目存储一个完整的 TLP 及其所有字段,包括序列号(12 位宽,占用 2 个字节)、头部(最多 16 字节)、可选的数据负载(最多 4 KB)、可选的 ECRC(4 个字节)以及 LCRC 字段(4 个字节)。
需要特别指出的是,规范以这种方式描述重放缓冲区,但这并非规范要求的实现方式。只要您的设备能够按照规范定义在需要时重放一系列 TLP,那么设备内部如何实现这一功能完全由设计者决定。采用上述方式运作的重放缓冲区只是实现该功能的一种方法。
10.3.1.3.2 重放缓冲区大小调整
规范编写者选择不指定重放缓冲区的大小,将其作为设备设计者的优化项。该缓冲区应足够大,以存储尚未收到 Ack的 TLP,从而在正常操作条件下不会填满并阻塞来自事务层的新 TLP,同时也要足够小以控制成本。为确定最佳缓冲区大小,设计者需考虑:
- 来自接收端的 Ack DLLP 延迟。
- 物理链路造成的延迟。
- 接收端从 L0s 状态退出到 L0 的延迟。换言之,缓冲区应足够大,以便在链路从 L0s 状态恢复到 L0 期间,能够容纳 TLP 而不发生阻塞。
当发送端接收到 Ack 时,它会从重放缓冲区中清除序列号等于或早于 Ack 中序列号的 TLP(通常应使用”小于”这一表述,但由于计数器回绕行为有时会导致该判断不准确,因此选用了”早于”这一术语)。类似地,当发送端接收到 Nak 时,它仍会清除重放缓冲区中序列号等于或早于 Nak 中序列号的 TLP,但随后会重发(重新发送)序列号更晚的 TLP(即重放缓冲区中剩余的 TLP)。
10.3.1.4 发送端对 Ack DLLP 的响应
接收方返回的单个 Ack 可确认多个 TLP;并非每个传输的 TLP 都需要专用 Ack。接收方可在收到多个有效 TLP 后,发送一个包含最后接收有效 TLP 序列号的 Ack。当发送端收到能推进进度的 Ack(其序列号晚于上次记录的序列号)时,会将 ACKD_SEQ 寄存器加载为该新 Ack 的序列号,同时重置 REPLAY_NUM 计数器和 REPLAY_TIMER,并从重放缓冲区中清除该 Ack 已确认的所有 TLP。
10.3.1.5 Ack/Nak 示例
10.3.1.5.1 示例 1:
以下讨论请参考第 332 页的图 10-8。
- 设备 A 发送序列号为 3、4、5、6、7 的 TLP。
- 设备 B 成功接收 TLP 3,并将其 NEXT_RCV_SEQ 计数器从 3 递增至 4。由于设备 B 此前已确认所有成功接收的 TLP,AckNak_LATENCY_TIMER 并未运行。在接收到 TLP 3 后,设备 B 现已成功接收一个尚未确认的 TLP,因此启动 AckNak_LATENCY_TIMER(相当于调度一次 Ack 确认)。
- 在 AckNak_LATENCY_TIMER 超时前,设备 B 成功接收 TLP 4 和 5。接收 TLP 4 和 5 不会重置 AckNak_LATENCY_TIMER。
- 当 AckNak_LATENCY_TIMER 超时后,设备 B 发送一个包含序列号 5(即最后接收的正确 TLP)的单一 Ack 确认。AckNak_LATENCY_TIMER 被重置,但需待成功接收 TLP 6 后方可重新启动。
- 设备 A 收到 Ack 5 后,因正向传输进度得以推进,重置 REPLAY_TIMER 和 REPLAY_NUM 计数器。同时从重放缓冲区中清除序列号小于或等于 5 的 TLP。
- 一旦设备 B 接收到 TLP 6 和 7,且其 AckNak_LATENCY_TIMER 再次超时,它将发送一个序列号为 7 的 Ack,该 Ack 将清除设备 A 重放缓冲区中的最后两个 TLP(根据此示例)。
图 10-8:示例 1 - Ack 示例
10.3.1.5.2 示例 2
此示例展示了与示例 1完全相同的行为,但重点指出了序列号的翻转行为,如图 10-9(第 333 页)所示。
- 设备 A 发送序列号为 4094、4095、0、1 和 2 的 TLP,其中 TLP 4094 是本示例中发送的第一个 TLP,TLP 2 是发送的最后一个 TLP。
- 设备 B 成功接收了序列号为 4094、4095、0、1 的 TLP,顺序依次如此。接收 TLP 4094 时,AckNak_LATENCY_TIMER 开始计时。在 AckNak_LATENCY_TIMER 超时前,TLP 4095、0 和 1 被接收。TLP 2 仍在传输途中。
- 由于 AckNak_LATENCY_TIMER 超时,设备 B 发送一个序列号为 1 的 Ack,以确认收到 TLP 1 及之前的所有 TLP(本例中为 0、4095 和 4094)。
- 设备 A 成功接收 Ack 1,从重放缓冲区中清除 TLP 4094、4095、0 和 1,并重置 REPLAY_TIMER 和 REPLAY_NUM 计数。
图 10-9:示例 2 - 序列号翻转的确认
10.3.1.6 发送端对 Nak 的响应
Nak 表示发生了问题。当发送端收到 Nak 时,它首先从重放缓冲区中清除所有序列号小于或等于该 Nak 序列号的 TLP,然后从 Nak 中序列号之后的下一个序列号开始重放剩余的 TLP。如果 Nak 导致至少一个 TLP 从缓冲区中被清除,则说明我们取得了进展。在这种情况下,发送端重置 REPLAY_NUM 计数器和 REPLAY_TIMER,并将 ACKD_SEQ 寄存器加载为 Nak 的序列号。
10.3.1.7 TLP 重放
当需要重放时,发送端会阻止其事务层接受新的 TLP。随后,它会按照 TLP 存入缓冲区的原始顺序(类似先进先出队列)重放缓冲区中必要的 TLP。重放事件结束后,数据链路层解除对事务层接受新 TLP 的阻塞。 被重放的 TLP 将保留在缓冲区中,直到后续某个时刻最终获得确认。
10.3.1.8 高效的 TLP 重放机制
重放过程中接收到的 Ack 或 Nak DLLP 必须得到处理。因此存在两种主要方案:发送端可暂存这些 DLLP 直至重放完成,再评估 Ack 或 Nak 并采取相应措施;另一种方案则是在重放期间就开始处理新接收的 Ack/Nak DLLP。若采用后者,新收到的 Ack 可能在重放进行时清除缓冲区中的部分条目,从而减少需要重放的 TLP 数量并节省链路时间。这种做法虽被允许,但需谨记:一旦 TLP 开始传输,就必须完成整个传输过程。
10.3.1.9 Nak 的示例
请参考第 335 页的图 10-10。
设备 A 发送序列号为 4094、4095、0、1 和 2 的 TLP。
设备 B 无错误地接收 TLP 4094,将 NEXT_RCV_SEQ 计数递增至 4095,并启动 AckNak_LATENCY_TIMER。
设备 B 在接收到的下一个 TLP(TLP 4095)中检测到 CRC 错误,并设置 NAK_SCHEDULED 标志,这将导致发送一个序列号为 4094(NEXT_RCV_SEQ 计数减 1)的 Nak。设备 B 不会等待 AckNak_LATENCY_TIMER 超时后再发送 Nak,通常会在下一个数据包边界发送。实际上,由于 Nak 已被调度发送,AckNak_LATENCY_TIMER 会被停止并重置。
设备 B 将继续评估传入的 TLP,寻找 TLP 4095。然而,由于设备 A 尚未意识到存在问题,它已经发送了数据包 0、1 和 2,设备 B 将会接收到这些数据包。但设备 B 不会接受它们,即使这些 TLP 本身是有效的(即通过 LCRC 检查)。这是因为所有数据包必须按顺序接收。因此,设备 B 会直接丢弃这些数据包,因为它们被视为乱序数据包,但不会额外发送 Nak。即使其中一个或多个 TLP 未通过 LCRC 检查,也不会发送额外的 Nak。NAK_SCHEDULED 标志已设置,只有在设备 B 成功接收到它期望的 TLP(本例中为 TLP 4095)后,该标志才会被清除。
设备 A 接收到 Nak 4094,并从重放缓冲区中清除 TLP 4094 及更早的 TLP(本例中无更早的 TLP)。同时,由于传输进度已推进,它会重置 REPLAY_TIMER 和 REPLAY_NUM 计数器。
由于接收到的确认 DLLP 是 Nak 而非 Ack,设备 A 随后重放重放缓冲区中所有剩余的 TLP(TLP 4095、0、1 和 2),并重新启动 REPLAY_TIMER,同时将 REPLAY_NUM 计数器加一。
一旦设备 B 接收到重放的 TLP 4095,它将清除 NAK_SCHEDULED 标志,递增 NEXT_RCV_SEQ 计数器,并启动 AckNak_LATENCY_TIMER。
图 10-10:Nak 示例
10.3.1.10 TLP 的重复重放
10.3.1.10.1 概述
每次发送端收到 Nak 时,它会重放缓冲区内容,并且 2 位 REPLAY_NUM 计数器会递增以记录重放事件的数量。上一示例中由 Nak 引起的重放将使 REPLAY_NUM 递增。
然而,如果重放未能解决问题,我们将进入一种新情况。接收端已设置 NAK_SCHEDULED 标志,并且在看到有问题的 TLP 被正确接收之前,无法再发送任何 Ack 或 Nak。如果重放因某种原因未能实现这一点,则接收端将不会做出响应。此时,发送端的 REPLAY_TIMER 将发挥作用。当它超时时,重放缓冲区的全部内容将被重新发送,REPLAY_NUM 计数器将递增,并且 REPLAY_TIMER 将被重置并重新启动。如果 REPLAY_TIMER 在未收到指示前进进度的 Ack 或 Nak 的情况下到期,此重放过程最多可重复三次。如果第三次重放后仍无前进进度且 REPLAY_TIMER 再次到期,这将导致 REPLAY_NUM 计数器从 3 回滚至 0。
10.3.1.10.2 重放编号翻转
当发生这种情况时,假设链路一定存在问题,因此链路层会触发物理层重新训练链路,使其进入恢复状态(参见第 571 页的“恢复状态”)。如果实现了可选的高级错误报告寄存器,则重放编号翻转错误状态位也将被置位(参见第 688 页的“高级可纠正错误处理”)。在重新训练过程中,重放缓冲区的内容会被保留,且链路层不会被初始化(这仅仅是重新训练链路,而非执行链路复位)。当重新训练完成后,发送端会再次尝试相同的重放过程,希望问题已消除,TLP 能够成功重放。
该规范并未描述在链路训练未能解决问题时,设备应如何处理重复的翻转事件。作者曾见过市面上某些商用硬件缺乏检测此状况的机制,最终陷入无限重训练的死循环。因此,建议设备记录重训练尝试次数是合理的。当尝试次数达到阈值后,设备可通过发出不可纠正的致命错误信号或中断来通知软件当前状况。
10.3.1.11 重放定时器
只要存在已发送但尚未获得确认的 TLP,发送端的 REPLAY_TIMER 就会持续运行。REPLAY_TIMER 的目标是确保 TLP 能够及时得到确认。若该定时器超时,则表明在此时刻本应收到 Ack 或 Nak 信号,说明传输过程必然出现了问题。从发送端角度而言,解决方案是执行重放操作,即重新发送重放缓冲区中的所有数据。
基于该定时器的用途,其超时值应与接收端中的 AckNak_LATENCY_TIMER 相关联。实际上,REPLAY_TIMER 的时长恰好是 AckNak_LATENCY_TIMER 的三倍。
规范中的公式决定了该定时器的计数值。其超时将触发重放事件,并使 REPLAY_NUM 计数器递增。可能发生超时的几种情况包括:Ack 或 Nak 在传输途中丢失,或接收端因错误而无法返回 Ack 或 Nak。与定时器相关的规则如下:
- 若定时器尚未运行,则在任意 TLP 的最后一个符号被传输时启动。
- 在以下情况下,定时器将被重置并重新启动:
- 收到表示前进进度的 Ack,且重放缓冲区中仍有未确认的 TLP。
- 发生重放事件,并且第一个重放 TLP 的最后一个符号已发送。
- 在以下情况下,定时器被重置并保持:
- 没有 TLP 需要传输,或重放缓冲区为空。
- 收到 Nak;定时器将在第一个重放 TLP 的最后一个符号发送时重新启动。
- 定时器超时;定时器将在第一个重放 TLP 的最后一个符号发送时重新启动。
- 数据链路层处于非活动状态。
- 在链路训练或重新训练期间,定时器保持暂停状态。
10.3.1.11.1 REPLAY_TIMER 计算公式
超时值主要取决于最大数据负载和链路宽度。以下给出了以符号时间计算重放计时器值的方程。请注意,该值仅为 Ack/Nak 延迟值的三倍。
$$ \text{REPLAY\_TIMER}=\left(\frac{(\text{Max\_Payload\_Size}+\text{TLPOverhead})\times\text{AckFactor}}{\text{LinkWidth}}+\text{InternalDelay}\right)\times 3+\text{Rx\_L0s\_Adjustment} $$
注:
Rx_L0s_Adjustment项在 Gen2 及后续版本中已移除。
方程字段定义如下:
最大有效载荷大小 - 设备控制寄存器中的值。对于具有不同最大有效载荷大小的多个功能,规范建议使用其中最小的值。
TLP 开销 - 数据负载之外的额外 TLP 字段(序列号、头部、摘要、LCRC 以及起始/结束帧符号)。在规范中,开销值被视为一个常数为 28 个符号。
AckFactor (AF) - 本质上是一个修正因子,表示在必须发送 Ack 之前,可以接收的最大有效载荷 TLP 数量。AF 值范围为 1.0 至 3.0,旨在平衡链路带宽效率与重放缓冲区大小。第 339 页的图 10-11 中的表格展示了不同链路宽度和有效载荷大小对应的 Ack Factor 值。这些 Ack Factor 值经过精心选择,使实现方案能够在无需配备昂贵的大容量缓冲区的情况下获得良好性能。
LinkWidth - 范围从 x1(1 位宽)到 x32(32 位宽)。
InternalDelay - 接收端处理 TLP 以及发送端处理 DLLP(确认)的内部延迟。该值在规范中以符号时间定义,并取决于链路速度:Gen1 = 19,Gen2 = 70,Gen3 = 115。
Rx_L0s_Adjustment - 该值曾包含在 1.x 版本的 PCIe 规范中,但在 2.0 及后续版本中被移除。它可用于补偿接收电路从 L0s 状态退出到 L0 状态所需的时间。设置链路控制寄存器中的扩展同步位会影响从 L0s 退出的时间,因此在进行此调整时必须予以考虑。有趣的是,规范编写者在创建重放定时器值表时,选择假定该值为零。更多内容将在下一节中讨论。
10.3.1.11.2 REPLAY_TIMER 汇总表
第 339 页的图 10-11 是 Gen1 速率的汇总表,显示了重放定时器方程中各变量在不同取值下的定时器加载值。对于较新版本的规范,这些数值已发生变化,新的表格及相关讨论可在第 350 页的“较新规范版本的时间差异”一节中找到。表中所有数值的容差范围为 -0% 到 +100%。
请注意,规范中的表格值(此处为方便起见复制)被视为“未调整”值,因为它们省略了方程中涉及从 L0s 恢复时间的最后一项。规范中对此未作解释,但如果链路必须从 L0s 唤醒至 L0,仅为了在超时可能为错误的情况下重传数据包,这将导致糟糕的电源管理。
避免此问题的一个简单方法是让发送端在进入 L0s 前确保重放缓冲区为空。规范要求进入 L1 时必须执行此步骤,但对 L0s 并无此要求,原因可能与相关风险有关。进入 L1 后恢复到 L0 需要更长的恢复过程,且存在一定的失败风险。若恢复失败,物理层状态机将不得不执行更多链路训练步骤,此过程会向链路层清除 LinkUp 标志,导致链路层重新初始化。若此时重放缓冲区中仍有数据,这些数据将丢失并引发问题。相比之下,从 L0s 恢复的风险显然被认为足够低,因此无需此要求。然而,在构建表格时却遗漏了 L0s 延迟数据,令读者对此产生疑惑。作者认为,规范制定者期望设计者采取措施,确保重放定时器超时不会在 L0s 状态下发生(通过在进入 L0s 前清空重放缓冲区),或当确认路径处于 L0s 状态时延迟超时。
图 10-11:Gen1 未调整的 REPLAY_TIMER 值
| 最大有效载荷大小 | x1 链路 | x2 链路 | x4 链路 | x8 链路 | x12 链路 | x16 链路 | x32 链路 |
|---|---|---|---|---|---|---|---|
| 128 字节 | 711 | 384 | 219 | 201 | 174 | 144 | 99 |
| 256 字节 | 1248 | 651 | 354 | 321 | 270 | 216 | 135 |
| 512 字节 | 1677 | 867 | 462 | 258 | 327 | 258 | 156 |
| 1024 字节 | 3213 | 1635 | 846 | 450 | 582 | 450 | 252 |
| 2048 字节 | 6285 | 3171 | 1614 | 834 | 1095 | 834 | 444 |
| 4096 字节 | 12429 | 6243 | 3150 | 1602 | 2118 | 1602 | 828 |
该表格汇总了使用公式计算得到的值,并减去了 Rx_L0s_Adjustment 项。
示例:假设链路宽度为 x2,最大有效载荷为 2048 字节,AckFactor = 1.0,Gen1 的 InternalDelay = 19,则:
$$ \left(\frac{(2048+28)\times 1.0}{2}+19\right)\times 3=3171 $$
在 Gen1 下,一个符号时间为 4 ns,因此超时时间约为 12.7 μs。
10.3.1.12 发送端 DLLP 处理
Ack/Nak 错误检测模块负责检查接收到的 DLLP 的 16 位 CRC 是否存在错误。若检测到错误,该 DLLP 将被丢弃。这被视为可纠正错误,并可能已在可选的增强错误报告寄存器中配置上报(参见第 688 页”可纠正错误处理”中的”错误 DLLP”),但由于这并非实质性问题,因此不会采取进一步操作。后续成功接收的同类型 DLLP 将使计数器恢复正常。因此,TLP 可能比预期稍晚被清除,或重放操作可能延迟发生,但不会丢失任何信息。当然,如果成功确认之间的延迟过大,可能导致 REPLAY_TIMER 超时,从而触发 TLP 重放。
10.3.2 接收端协议详情
10.3.2.1 物理层
在物理层接收到的 TLP 会进行接收错误检查(如帧错误、不一致错误和无效符号)。若该层级存在错误,TLP 将被丢弃,链路层可能通过特定设计方法获知此情况,以便安排 Nak 并重放数据包。若链路层未获知该错误,最终将检测到序列号违规,进而触发 Nak 和重放操作。
图 10-12:Ack/Nak 接收端组件
10.3.2.2 TLP LCRC 校验
若物理层未检测到错误,链路层将首先检查 CRC 错误。接收端根据接收到的 TLP(不含 LCRC 字段)计算预期的 LCRC 值,并将该值与 TLP 的 32 位 LCRC 进行比对。若两者匹配,则 TLP 有效;否则丢弃该 TLP,并由接收端调度 Nak 信号。
10.3.2.3 下一个接收 TLP 的序列号
若 LCRC 校验正确,接收端接下来会将 NEXT_RCV_SEQ 计数器与待接收 TLP 中应包含的序列号进行比对。在正常操作条件下,这两个数值应保持一致。若匹配成功,接收端将 TLP 转发至事务层,递增 NEXT_RCV_SEQ 计数器,并调度 Ack 信号。
如果接收到的 TLP 序列号早于或晚于 NEXT_RCV_SEQ 计数,则会出现以下两种情况之一:重复 TLP 或乱序 TLP。
重复 TLP。如果传入数据包的序列号早于(逻辑上更小)预期值,则意味着发送端决定重新发送接收方已经接收过的数据包。虽然这种重传会浪费链路时间,但重复数据包本身并非错误。这种情况可能是由于前一个 TLP 的 Ack 或 Nak 超时导致的。当接收方检测到重复数据包时,会将其丢弃,并调度一个 Ack,其中包含其收到的最后一个有效 TLP 的序列号(该序列号可能与重传 TLP 中的序列号不同)。
乱序 TLP。如果传入数据包的序列号晚于(逻辑上更大)预期值,唯一解释是某个 TLP 必定已丢失。这是一个可纠正的错误,通过发送 Nak 来处理。无论传入数据包是否完好都无关紧要,因为数据包只能按正确的序列号顺序被接收。该数据包将被丢弃,接收端等待具有预期序列号的 TLP。
当接收到存在 CRC 错误、被作废或序列号检查失败的 TLP 时,NEXT_RCV_SEQ 计数器不会递增。
发送端根据 PCI 排序规则对 TLP 进行排序,以维持正确的程序流程并避免潜在的死锁和活锁情况(参见第 285 页第 8 章“事务排序”)。接收端必须保持此顺序,并应用以下三条规则:
- 当接收端检测到错误的 TLP 时,它会丢弃该 TLP 以及管道中后续的所有新 TLP,直到检测到重放的 TLP。
- 重复的 TLP 将被丢弃。
- 在等待丢失或损坏的 TLP 期间收到的 TLP 将被丢弃。
10.3.2.4 接收方调度一个确认 DLLP
如果接收方的数据链路层在传入的 TLP 中未检测到错误,则将该 TLP 转发至事务层。NEXT_RCV_SEQ 计数器递增,接收方启动 AckNak_LATENCY_TIMER(假设该定时器尚未运行)。这相当于“调度一个确认”。接收方允许在 AckNak_LATENCY_TIMER 到期前继续接收正确的 TLP 而无需发送确认。当定时器到期时,
仅发送一个带有最后一个正确 TLP 序列号的确认,确认已正确接收所有序列号不超过当前确认中序列号的 TLP。这种技术通过减少Ack/Nak 流量来提高链路效率。回顾一下,这种技术之所以有效,是因为 TLP 必须始终按顺序成功接收。
10.3.2.5 接收端调度一个 Nak
如前文在讨论接收端逻辑时所述(参见第 324 页“接收端要素”),当接收端检测到 TLP 上存在错误时,它会丢弃该错误数据包,并在 NAK_SCHEDULED 标志为清除状态时将其置位,这将导致调度一个序列号为 NEXT_RCV_SEQ 计数值减 1 的 Nak。由于此时已调度 Nak,AckNak_LATENCY_TIMER 定时器将被重置并停止。调度 Nak 可视为一种“边沿触发”事件,而非电平触发事件。正是检测到 NAK_SCHEDULED 标志的上升沿才会触发 Nak 调度。在下一次上升沿到来前无法发送另一个 Nak,这意味着必须先清除 NAK_SCHEDULED 标志(下降沿)。只有两种事件会清除 NAK_SCHEDULED 标志:第一种是成功接收到预期的下一个 TLP(序列号与 NEXT_RCV_SEQ 计数值匹配的 TLP);第二种是链路复位(非重新训练,而是复位)。
尽管尽快将 Nak 发送至发送端至关重要(在未发现错误的失败 TLP 被确认之前,无法接受其他 TLP),但其他正在传输中的 TLP、DLLP 或有序集可能优先级高于 Nak,这意味着接收端必须延迟 Nak 的发送直至这些传输完成(参见第 350 页”推荐的数据包调度优先级”)。在此期间,若有其他 TLP 到达接收端,它们将被丢弃,且在 NAK_SCHEDULED 标志置位期间不会调度额外的 Ack 或 Nak。
10.3.2.6 AckNak_LATENCY_TIMER
该定时器定义了接收端在必须为成功接收的 TLP(或一组 TLP)发送 Ack 之前可等待的最长时间。如前所述,每当接收端成功接收到尚未确认的 TLP 时,该定时器即开始运行。一旦定时器到期,将调度一个包含最后正确接收 TLP 序列号的 Ack 进行传输。调度 Ack 会重置 AckNak_LATENCY_TIMER,且该定时器仅在下一个 TLP 成功接收后才会重新开始计数。
10.3.2.6.1 AckNak_LATENCY_TIMER 计算公式
AckNak_LATENCY_TIMER 的超时值由规范定义,并根据协商的链路宽度和启用的最大有效载荷大小而变化。
定义超时的公式如下所示:
$$ \text{AckNak\_LATENCY\_TIMER}=\frac{(\text{Max\_Payload\_Size}+\text{TLPOverhead})\times\text{AckFactor}}{\text{LinkWidth}}+\text{InternalDelay}+\text{Tx\_L0s\_Adjustment} $$
注:
Tx_L0s_Adjustment项在 Gen2 及后续版本中已移除。
定时器中的值以符号时间表示,即通过链路发送一个符号所需的时间:Gen1 为 4 ns,Gen2 为 2 ns,Gen3 为 1 ns。
方程字段包括:
- Max_Payload_Size - 设备控制寄存器中的值。当存在多个功能且具有不同的 Max_Payload_Size 值时,规范建议使用其中最小的值。
- TLPOverhead - 数据负载之外的额外 TLP 字段(序列号、报头、摘要、LCRC 以及起始/结束帧定界符号)。在规范中,开销值被视为一个 28 个符号的常量。
- AckFactor (AF) - 本质上是一个修正因子,表示在必须发送 Ack 之前可以接收的最大有效载荷大小 TLP 数量。AF 值范围从 1.0 到 3.0,旨在平衡链路带宽效率和重放缓冲区大小。第 339 页的图 10-11 中的表格显示了不同链路宽度和负载大小下的 Ack Factor 值。这些 Ack Factor 值的选择旨在使实现能够在无需配备大型不经济缓冲区的情况下获得良好性能。
- LinkWidth - 范围从 x1(1 位宽)到 x32(32 位宽),对应 1 到 32 条通道。
- InternalDelay - 接收端内部处理 TLP 以及发送端内部处理 DLLP(确认包)的内部延迟。该值在规范中以符号时间定义,并取决于链路速度:Gen1 = 19,Gen2 = 70,Gen3 = 115。
- Tx_L0s_Adjustment - 该值包含在 1.x 版本的 PCIe 规范中,但在 2.0 及后续版本中被移除。它可用于计算接收电路从 L0s 状态退出到 L0 状态所需的时间。设置链路控制寄存器的扩展同步位会影响 L0s 的退出时间,在调整时必须考虑此因素。有趣的是,规范制定者在创建重放定时器值表时选择假定该值为零。
10.3.2.6.2 AckNak_LATENCY_TIMER 汇总表
第 345 页的表 10-2 展示了 Gen1 定时器加载值,这些值对应 AckNak_LATENCY_TIMER 方程中所有可能使用的数值。更高的数据速率会改变方程及由此生成的表格(参见第 350 页的“较新规范版本的时间差异”)。与重放定时器表类似,该表的构建假设方程中的 L0s 调整值为零,并将所得值称为“未调整值”。请注意,表中所有数值的公差范围为 -0% 到 +100%。
表 10-2:Gen1 未调整的 Ack 传输延迟
| 最大有效载荷大小 | x1 链路 | x2 链路 | x4 链路 | x8 链路 | x12 链路 | x16 链路 | x32 链路 |
|---|---|---|---|---|---|---|---|
| 128 字节 | 237 (AF=1.4) | 128 (AF=1.4) | 73 (AF=1.4) | 67 (AF=2.5) | 58 (AF=3.0) | 48 (AF=3.0) | 33 (AF=3.0) |
| 256 字节 | 416 (AF=1.4) | 217 (AF=1.4) | 118 (AF=1.4) | 107 (AF=2.5) | 90 (AF=3.0) | 72 (AF=3.0) | 45 (AF=3.0) |
| 512 字节 | 559 (AF=1.0) | 289 (AF=1.0) | 154 (AF=1.0) | 86 (AF=1.0) | 109 (AF=2.0) | 86 (AF=2.0) | 52 (AF=2.0) |
| 1024 字节 | 1071 (AF=1.0) | 545 (AF=1.0) | 282 (AF=1.0) | 150 (AF=1.0) | 194 (AF=2.0) | 150 (AF=2.0) | 84 (AF=2.0) |
| 2048 字节 | 2095 (AF=1.0) | 1057 (AF=1.0) | 538 (AF=1.0) | 278 (AF=1.0) | 365 (AF=2.0) | 278 (AF=2.0) | 148 (AF=2.0) |
| 4096 字节 | 4143 (AF=1.0) | 2081 (AF=1.0) | 1050 (AF=1.0) | 534 (AF=1.0) | 706 (AF=2.0) | 534 (AF=2.0) | 276 (AF=2.0) |
10.4 更多示例
在课堂环境中,示例通常能让人更容易理解Ack/Nak 过程,因此这里提供了一些示例来说明特殊情况。
10.4.1 丢失的 TLP
请参考第 346 页的图 10-13,该图展示了如何检测和处理丢失的 TLP。
- 设备 A 发送 TLP 4094、4095、0、1 和 2。
- 设备 B 成功接收 TLP 4094,因此启动其 AckNak_LATENCY_TIMER 并递增 NEXT_RCV_SEQ 计数。随后,它还接收了 TLP 4095 和 0。
- 接收 TLP 0 后,AckNak_LATENCY_TIMER 超时,导致其调度一个序列号为 0 的 Ack。
- 设备 A 收到 Ack 0 后,从重放缓冲区中清除 TLP 4094、4095 和 0。
- TLP 1 在传输途中因某种原因丢失(可能是物理层丢弃了它),而 TLP 2 却到达了。序列号检查显示,设备 B 发现 TLP 2 的序列号不等于 NEXT_RCV_SEQ 计数,但处于乱序范围内。
- 设备 B 丢弃 TLP 2,并设置 NAK_SCHEDULED 标志,该标志将在此情况下发送 Nak 0(NEXT_RCV_SEQ 计数 - 1)。
- 收到 Nak 0 后,设备 A 重放 TLP 1 和 TLP 2。它会清除重放缓冲区中的 TLP 0 及更早的 TLP,但由于这些 TLP 此前已被移除,因此该操作变得不必要。
- TLP 1 和 TLP 2 无差错地到达设备 B,并被转发至事务层。
图 10-13:处理丢失的 TLP
10.4.2 错误确认
第 347 页的图 10-14展示了处理损坏确认的协议。
- 设备 A 发送 TLP 4094、4095、0、1 和 2。
- 设备 B 接收到 TLP 4094、4095 和 0,将 NEXT_RCV_SEQ 设置为 1,并由于 AckNak_LATENCY_TIMER 已超时而返回 Ack 0。
- Ack 0 在链路上传输过程中出现比特错误,因此当设备 A 检查其 16 位 CRC 时,校验失败并被丢弃。这意味着 TLP 4094、4095 和 0 仍保留在设备 A 的重放缓冲区中。
- TLP 1 和 2 成功到达设备 B,因此 NEXT_RCV_SEQ 计数递增至 3,并在 AckNak_LATENCY_TIMER 再次超时后返回 Ack 2。
- Ack 2 安全到达设备 A,设备 A 从其重放缓冲区中清除 TLP 4094、4095、0、1 和 2。
如果 Ack 2 也丢失或损坏,且没有进一步的 Ack 或 Nak DLLP 返回设备 A,其重放定时器超时将触发整个缓冲区的重放。设备 B 看到 TLP 4094、4095、0、1 和 2,并将其视为重复数据(其序列号早于下一个接收序列计数 3)。这些数据将被丢弃,并且由于重复数据包,设备 A 将再次收到 Ack 2。
图 10-14:处理错误确认
10.4.3 损坏的 Nak
第 349 页的图 10-15 展示了处理损坏 Nak 的协议。
- 设备 A 传输 TLP 4094、4095、0、1 和 2。
- 设备 B 成功接收 TLP 4094、4095 和 0(且 AckNak_LATENCY_TIMER 尚未到期)。下一个接收到的 TLP 未通过 LCRC 校验,因此设备 B 设置 NAK_SCHEDULED 标志,并重置并保持 AckNak_LATENCY_TIMER。Nak 以最后成功接收的 TLP 序列号 0 被传回。
- Nak 0 在设备 A 处未通过 16 位 CRC 校验并被丢弃。
- 此时,设备 B 将不再发送任何 Ack 或 Nak,直到它成功接收到下一个预期的 TLP(本例中为 TLP 1)。然而,这需要一次重放。设备 A 尚不知道需要重放,因为之前发送的一个 Nak 已损坏并被丢弃。这一问题将通过 REPLAY_TIMER 解决。由于在指定时间范围内未收到任何表明进度推进的 Ack 或 Nak,REPLAY_TIMER 最终将超时。
- 一旦 REPLAY_TIMER 超时,设备 A 将重放重放缓冲区中的所有 TLP,递增 REPLAY_NUM 计数,并重置和重新启动 REPLAY_TIMER。
- 设备 B 将接收到 TLP 4094、4095 和 0,并识别出它们是重复的。重复的 TLP 将被丢弃,并安排一个序列号为 0 的 Ack(表示已取得的最远进度)。
- 当设备 B 成功接收到 TLP 1 后,它将清除 NAK_SCHEDULED 标志,递增 NEXT_RCV_SEQ,并重新启动 AckNak_LATENCY_TIMER,因为它已成功接收到一个尚未确认的 TLP。
图 10-15:处理损坏的 Nak
10.5 Ack/Nak 协议处理的错误情况
Ack/Nak 协议通过多种纠错机制,确保事务层数据包在面临多种潜在错误时仍能可靠传输。以下错误列表包含了用于解决这些错误的纠正机制。
- TLP 中的 LCRC 错误。解决方案:接收端检测到 LCRC 错误,并调度一个包含 NEXT_RCV_SEQ 计数减 1 的 Nak。作为响应,发送端至少重放一个 TLP,从失败的那个开始。
- TLP 在传输至接收端数据链路层的过程中丢失(例如,物理层检测到数据包问题并将其丢弃)。解决方案:接收端检查所有接收到的 TLP 的序列号,期望它们按顺序到达下一个序列号。如果某个 TLP 丢失,则下一个成功到达的 TLP 的序列号将出现乱序。作为响应,接收端调度一个 NRS 计数减 1 的 Nak,发送端至少重放一个 TLP,从丢失的那个开始。
- 损坏的 Ack 或 Nak 在传输至发送端的过程中丢失。解决方案:发送端检测到 DLLP 中的 CRC 错误(参见第 309 页的“接收端对 DLLP 的处理”),丢弃该数据包,并等待下一个数据包。
- Ack 情况:当后续收到一个序列号更大的 Ack 时,发送端重放缓冲区会清除所有序列号等于或早于该 Ack 的 TLP。发送端对此异常情况毫无察觉(重放缓冲区暂时填满的情况除外)。
- Nak 情况:接收端在设置 NAK_SCHEDULED 标志后,在成功接收到下一个预期的 TLP 之前不会发送新的 Nak 或 Ack,这意味着需要进行重放。当然,如果 Nak 丢失,发送端并不知道需要重放。此时,重放定时器最终会超时并触发重放。
- 在预期时间内未收到 Ack/Nak。解决方案:重放定时器超时触发重放。
- 接收端未能为已接收的 TLP 发送 Ack/Nak。解决方案:同样,发送端的重放定时器会超时并触发重放。
10.6 推荐的数据包调度优先级
设备在给定链路上可能传输多种类型的 TLP、DLLP 和有序集。推荐的数据包调度优先级如下:
- 完成当前正在进行的任何 TLP 或 DLLP(最高优先级)
- 有序集
- Nak
- Ack
- 流量控制
- 重放缓冲区重传
- 等待在事务层中的 TLP
- 所有其他 DLLP 传输(最低优先级)
10.7 较新规范版本的时间差异
如前所述,Ack/Nak 协议的定时器值在 Gen2 及后续规范版本中有所不同。为提升文本可读性,前述讨论仅包含 Gen1 版本(2.5 GT/s 速率),但为方便起见,此处将三个版本一并列出。
如前所述,给出的数值以符号时间为单位,因此实际时间等于该数值乘以在该速率下通过链路传输一个符号所需的时间。回顾一下,传输一个符号(称为符号时间)所需的时间为:Gen1 为 4 纳秒,Gen2 为 2 纳秒,Gen3 传输 1 字节为 1.25 ns。
10.7.1 确认传输延迟(AckNak 延迟)
各版本规范之间一个有趣的差异在于对 L0s 恢复时间的处理方式。在 1.x 版本规范中,AckNak_LATENCY_TIMER(Ack/Nak 延迟定时器)公式中包含了一个用于计算该时间的参数,但规范中基于该公式的表格将其值设为零,并将所得结果称为”未调整值”。从 2.0 版本规范开始,L0s 恢复值已完全从公式中移除,且文本明确指出接收端无需根据 L0s 退出延迟或扩展同步位的值来调整确认调度。所有表格数值均不包含 L0s 恢复分量,因此仍被称为”未调整值”。
请注意,由于所有表格中的 AF(Ack Factor)值相同,且已在之前展示 Gen1 表格时给出,因此本处表格不再包含这些值。
此外,与 Gen1 相同,所有表格数值的公差范围为 -0% 到 +100%。为说明这一点,第 351 页的表 10-3 列出了 x1 链路且最大有效载荷为 128 字节时的传输时间为 237 个符号周期。因此,合法数值范围应不低于 237 个符号周期,且不超过 474 个符号周期。
10.7.1.1 2.5 GT/s 操作
表 10-3:Gen1 未调整的 AckNak_LATENCY_TIMER 值(符号时间)
| 最大有效载荷大小 | x1 链路 | x2 链路 | x4 链路 | x8 链路 | x12 链路 | x16 链路 | x32 链路 |
|---|---|---|---|---|---|---|---|
| 128 字节 | 237 | 128 | 73 | 67 | 58 | 48 | 33 |
| 256 字节 | 416 | 217 | 118 | 107 | 90 | 72 | 45 |
| 512 字节 | 559 | 289 | 154 | 86 | 109 | 86 | 52 |
| 1024 字节 | 1071 | 545 | 282 | 150 | 194 | 150 | 84 |
| 2048 字节 | 2095 | 1057 | 538 | 278 | 365 | 278 | 148 |
表 10-3:Gen1 未调整的 AckNak_LATENCY_TIMER 值(符号时间)(续)
| 最大有效载荷大小 | x1 链路 | x2 链路 | x4 链路 | x8 链路 | x12 链路 | x16 链路 | x32 链路 |
|---|---|---|---|---|---|---|---|
| 4096 字节 | 4143 | 2081 | 1050 | 534 | 706 | 534 | 276 |
10.7.1.2 5.0 GT/s 操作
表 10-4:Gen2 未调整的 AckNak_LATENCY_TIMER 值(符号时间)
| 最大有效载荷大小 | x1 链路 | x2 链路 | x4 链路 | x8 链路 | x12 链路 | x16 链路 | x32 链路 |
|---|---|---|---|---|---|---|---|
| 128 字节 | 288 | 179 | 124 | 118 | 109 | 99 | 84 |
| 256 字节 | 467 | 268 | 169 | 158 | 141 | 123 | 96 |
| 512 字节 | 610 | 340 | 205 | 137 | 160 | 137 | 103 |
| 1024 字节 | 1122 | 596 | 333 | 201 | 245 | 201 | 135 |
| 2048 字节 | 2146 | 1108 | 589 | 329 | 416 | 329 | 199 |
| 4096 字节 | 4194 | 2132 | 1101 | 585 | 757 | 585 | 327 |
10.7.1.3 8.0 GT/s 操作
表 10-5:Gen3 未调整的 AckNak_LATENCY_TIMER 值(符号时间)
| 最大有效载荷大小 | x1 链路 | x2 链路 | x4 链路 | x8 链路 | x12 链路 | x16 链路 | x32 链路 |
|---|---|---|---|---|---|---|---|
| 128 字节 | 333 | 224 | 169 | 163 | 154 | 144 | 129 |
| 256 字节 | 512 | 313 | 214 | 203 | 186 | 168 | 141 |
| 512 字节 | 655 | 385 | 250 | 182 | 205 | 182 | 148 |
| 1024 字节 | 1167 | 641 | 378 | 246 | 290 | 246 | 180 |
| 2048 字节 | 2191 | 1153 | 634 | 374 | 461 | 374 | 244 |
| 4096 字节 | 4239 | 2177 | 1146 | 630 | 802 | 630 | 372 |
10.7.2 重放定时器
与 AckNak 延迟定时器的计算类似,在较新的规范版本中,L0s 恢复时间对重放定时器的处理方式有所不同。在 1.x 规范中,重放定时器方程中包含一个参数来考虑这一因素,但基于该方程的规范表格将该参数值设为零,并将结果值称为“未调整”。从 2.0 规范开始,该参数从方程中完全删除,文本指出,如果使用 L0s 退出,发送端应进行补偿,要么静态地将该时间添加到表格值中,要么检测链路是否处于该状态并在该情况下允许额外时间。表格值仍然不包含 L0s 分量,并仍被称为“未调整”。
关于此主题的最后一点,规范强烈建议,如果接收 Ack 的延迟可能是由另一设备的发送端处于 L0s 状态导致的,则发送端不应在重放定时器超时时执行重放。
请注意,与确认延迟定时器表一样,所有表值的容差范围为 -0% 到 +100%。为说明这一点,第 353 页的表 10-6 列出了 x1 链路且最大有效载荷大小为 128 字节时的时间为 711 个符号时间。因此,合法值的范围应不小于 711 个符号时间,且不超过 1422 个符号时间。
10.7.2.1 2.5 GT/s 操作
表 10-6:Gen1 未调整的 REPLAY_TIMER 值(以符号时间计)
| 最大有效载荷大小 | x1 链路 | x2 链路 | x4 链路 | x8 链路 | x12 链路 | x16 链路 | x32 链路 |
|---|---|---|---|---|---|---|---|
| 128 字节 | 711 | 384 | 219 | 201 | 174 | 144 | 99 |
| 256 字节 | 1248 | 651 | 354 | 321 | 270 | 216 | 135 |
| 512 字节 | 1677 | 867 | 462 | 258 | 327 | 258 | 156 |
| 1024 字节 | 3213 | 1635 | 846 | 450 | 582 | 450 | 252 |
| 2048 字节 | 6285 | 3171 | 1614 | 834 | 1095 | 834 | 444 |
| 4096 字节 | 12429 | 6243 | 3150 | 1602 | 2118 | 1602 | 828 |
10.7.2.2 5.0 GT/s 操作
表 10-7:Gen2 未调整的 REPLAY_TIMER 值(以符号时间计)
| 最大有效载荷大小 | x1 链路 | x2 链路 | x4 链路 | x8 链路 | x12 链路 | x16 链路 | x32 链路 |
|---|---|---|---|---|---|---|---|
| 128 字节 | 864 | 537 | 372 | 354 | 327 | 297 | 252 |
| 256 字节 | 1401 | 804 | 507 | 474 | 423 | 369 | 288 |
| 512 字节 | 1830 | 1020 | 615 | 411 | 480 | 411 | 309 |
| 1024 字节 | 3366 | 1788 | 999 | 603 | 735 | 603 | 405 |
| 2048 字节 | 6438 | 3324 | 1767 | 987 | 1248 | 987 | 597 |
| 4096 字节 | 12582 | 6396 | 3303 | 1755 | 2271 | 1755 | 981 |
10.7.2.3 8.0 GT/s 操作
表 10-8:Gen3 未调整的 REPLAY_TIMER 值
| 最大有效载荷大小 | x1 链路 | x2 链路 | x4 链路 | x8 链路 | x12 链路 | x16 链路 | x32 链路 |
|---|---|---|---|---|---|---|---|
| 128 字节 | 999 | 672 | 507 | 489 | 462 | 432 | 387 |
| 256 字节 | 1536 | 939 | 642 | 609 | 558 | 504 | 423 |
| 512 字节 | 1965 | 1155 | 750 | 546 | 615 | 546 | 444 |
| 1024 字节 | 3501 | 1923 | 1134 | 738 | 870 | 738 | 540 |
| 2048 字节 | 6573 | 3459 | 1902 | 1122 | 1383 | 1122 | 732 |
| 4096 字节 | 12717 | 6531 | 3438 | 1890 | 2406 | 1890 | 1116 |
10.8 Switch 直通模式
在介绍了协议的工作原理之后,现在是解释其一般操作中一个例外情况的好时机。PCIe 支持一种名为“直通模式”的交换机特性,可用于改善大型 TLP 通过交换机时的传输延迟。
10.8.1 背景
考虑一个大型 TLP 需要通过交换机的示例,如图 10-16(第 357 页)所示。由于入口交换机端口在查看整个 TLP 之前无法判断数据包是否存在错误,因此通常会先存储整个数据包并检查错误,然后再将其转发到出口端口。这种存储转发方法虽然可行,但对于大型数据包而言,通过交换机的延迟可能较大,这对某些应用来说可能是个问题。如果可能的话,最好能尽量减少这种延迟。
10.8.2 延迟优化选项
由于 TLP 的第一部分包含数据包的路由信息头部,一种可行的方案是假设数据包为有效数据包,并在完整接收数据包之前就开始评估头部中的路由信息。这将允许交换机在完成路由评估后立即开始将 TLP 转发至出口端口。随后,只要不会导致交换机内部出现欠载情况,出口端口即可开始将数据发送到其链路上。(若入口端口为 x1 而出口端口为 x16,则极易发生欠载:出口端口发送数据包的速度将远高于接收速度。)
当然,入口端口在接收到数据包末尾的 LCRC 之前无法检查数据包中的错误,因此存在一个较小的风险:正在转发的 TLP 可能实际上包含错误。最终,TLP 的末尾到达入口端口,此时可以对数据包进行检查。如果发现存在错误,入口端口会对错误的 TLP 采取常规处理方式,即发送一个 Nak 以请求重放该数据包。然而,我们现在面临一个问题:大部分已知有误的数据包已经被转发到了出口端口。此时我们有哪些选择?我们可以完成该数据包的转发,等待相邻接收端发现错误后发送 Nak,但重放缓冲区中的数据包将是错误的,因此重放无法解决问题。我们也可以截断正在传输中的错误数据包,但规范不允许这种操作。为了解决这个问题,我们需要另一种选择,而这就是直通模式发挥作用的地方。
10.8.3 直通模式操作
直通模式为上一节所述的转发问题提供了解决方案:如果在接收数据包时检测到错误,必须将已在发送途中的数据包“作废”。
作废的数据包以 EDB(错误结束)符号而非 END(正常结束)符号终止。为明确标识该状态,TLP 的 32 位 LCRC 被取反(按位求补)为原始计算值的补码。本质上,作废数据包会被视为从未存在过。在交换机的出口端口上,这意味着重放缓冲区会丢弃该数据包,并将 NEXT_TRANSMIT_SEQ 计数器减一(回滚)。
当设备接收到被识别为作废 TLP 的数据包时,会直接丢弃该数据包,将其视为从未存在。NEXT_RCV_SEQ 计数器不会递增,AckNak_LATENCY_TIMER 定时器不会启动,NAK_SCHEDULED 标志也不会置位。接收设备静默丢弃作废 TLP,且不返回任何 Ack/Nak 响应。
10.8.4 直通模式操作示例
第 357 页的图 10-16 展示了一个从左侧进入、经过交换机、最终到达右侧端点的 TLP。左侧链路上发生了 TLP 错误。具体步骤如下:
- 一个传入的 TLP 在交换机入口端口被检测到。该 TLP 在传输过程中已损坏,但此时尚未被发现。
- TLP 头部到达后被解码,在直通转发操作中,数据包被转发至目标出口端口。
- 最终,数据包尾部到达,交换机入口端口得以完成 LCRC 错误校验。它检测到 CRC 错误,并向 TLP 源端返回一个 Nak 信号。
- 在出口端口,交换机将损坏的 TLP 末尾的 END 帧定界符替换为 EDB,并反转计算出的 LCRC 值。该 TLP 现已被“作废”,交换机将其从重放缓冲区中丢弃。
- 作废的数据包到达端点。端点检测到 EDB 符号和反转的 LCRC,并静默丢弃该数据包,不返回 Nak 信号。
现在假设 TLP 源设备重放该数据包且未发生错误。与之前一样,TLP 以极短延迟被转发至出口端口。当 TLP 的其余部分到达交换机时,未出现错误,因此向 TLP 源返回一个 Ack,随后 TLP 源从其重放缓冲区中清除该 TLP。此时,交换机出口端口在其重放缓冲区中保留一份 TLP 副本。当 TLP 到达目的地时,数据包无错误,端点返回一个 Ack。基于此,交换机从其重放缓冲区中清除该 TLP 副本,至此序列完成。
图 10-16:交换机直通模式显示错误处理